﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using MobiMenu.Restaurant.Data;
using System.Configuration;
using System.Data.SqlClient;
using System.Linq.Expressions;
using System.Reflection;
using System.Collections.Specialized;
using MobiMenu.Restaurant.Web.Utility;
using System.Text;

namespace MobiMenu.Restaurant.Web
{
    /// <summary>
    /// Base Rest Service 的摘要说明
    /// </summary>
    public abstract class BaseRESTService<T> : IHttpHandler where T : class
    {
        private readonly string _connStr;

        private static object _lockDBManager = new object();

        public BaseRESTService()
        {
            _connStr = ConfigurationManager.ConnectionStrings[RequestHelper.MobiMenuConnectionString].ConnectionString;
        }

        #region IHttpHandler 成员

        public bool IsReusable
        {
            get 
            {
                return true;
            }
        }

        public void ProcessRequest(HttpContext context)
        {
            ResponseData<T> responseData = null;
            try
            {
                RESTContext<T> rESTContext = new RESTContext<T>(context);
                responseData = rESTContext.ResponseData;
                responseData.Success = InternalProcessRequest(rESTContext);
            }
            catch (Exception ex)
            {
                if (responseData == null)
                {
                    responseData = new ResponseData<T>();
                }

                responseData.Success = false;
                responseData.Error = ex.Message;
                LogHelper.WriteLog("ProcessRequest", ex);
            }

            var resultStringBuilder = new StringBuilder();
            resultStringBuilder.Append(JsonHelper<ResponseData<T>>.WriteObject(responseData));
            context.Response.Write(resultStringBuilder.ToString());
        }

        private bool InternalProcessRequest(RESTContext<T> context)
        {
            var requestData = context.RequestData;
            switch (requestData.Operation)
            {
                case OperationType.Select:
                case OperationType.SelectAll:
                    return ProcessSelectAll(context);
                case OperationType.Insert:
                    return ProcessInsert(context);
                case OperationType.Update:
                    return ProcessUpdate(context);
                case OperationType.Delete:
                    return ProcessDelete(context);
            }

            return false;
        }

        private bool ProcessUpdate(RESTContext<T> context)
        {
            var ps = context.RequestData.Params;
            if (ps.Count == 0)
            {
                return false;
            }

            using (SqlConnection sqlConn = new SqlConnection(_connStr))
            {
                var db = new DBManager<T>(sqlConn);
                Expression<Func<T, bool>> expr = null;
                if (GetUpdateWhereExpression(ps, out expr))
                {
                    var query = db.Table.Where(expr);
                    bool changed = false;
                    foreach(T item in query)
                    {
                        changed = UpdateEntity(ps, item) || changed;
                    }

                    if (changed)
                    {
                        db.SubmitChanges();
                        return true;
                    }
                }
            }

            return false;
        }

        protected abstract bool UpdateEntity(NameValueCollection nameValues, T item);

        protected abstract bool GetUpdateWhereExpression(NameValueCollection nameValues, out Expression<Func<T, bool>> expr);

        private bool ProcessDelete(RESTContext<T> context)
        {
            var requestData = context.RequestData;
            if (requestData.DeleteIds == null && requestData.DeleteIds.Count == 0)
            {
                return false;
            }

            using (SqlConnection sqlConn = new SqlConnection(_connStr))
            {
                var db = new DBManager<T>(sqlConn);
                Expression<Func<T, bool>> expr = null;
                if (GetDeleteWhereExpression(context, out expr))
                {
                    var query = db.Table.Where(expr);
                    db.Table.DeleteAllOnSubmit(query);
                    db.SubmitChanges();
                    return true;
                }
            }

            return false;
        }

        protected abstract bool GetDeleteWhereExpression(RESTContext<T> context, out Expression<Func<T, bool>> expr);

        private bool ProcessInsert(RESTContext<T> context)
        {
            var requestData = context.RequestData;
            if (requestData.InsertObjects == null && requestData.InsertObjects.Count == 0)
            {
                return false;
            }

            var responseData = context.ResponseData;
            using (SqlConnection sqlConn = new SqlConnection(_connStr))
            {
                var db = new DBManager<T>(sqlConn);
                db.Table.InsertAllOnSubmit(requestData.InsertObjects);
                db.SubmitChanges();
            }

            return true;
        }

        private bool ProcessSelectAll(RESTContext<T> context)
        {
            var requestData = context.RequestData;
            var responseData = context.ResponseData;
            responseData.CreateData();
            using (SqlConnection sqlConn = new SqlConnection(_connStr))
            {
                var db = new DBManager<T>(sqlConn);
                IQueryable<T> query = db.Table;
                if (requestData != null && requestData.Params.Count > 0)
                {
                    query = query.Where(GetWhereExpression(requestData.Params));
                }

                int total = query.Count();
                responseData.Data.Total = total;

                string sortField = null;
                if (requestData != null && requestData.NeedSort())
                {
                    sortField = requestData.SortField;
                }
                else
                {
                    sortField = GetDefaultSortField();
                }

                if (!string.IsNullOrEmpty(sortField))
                {
                    bool isDesc = requestData.SortByDesc.HasValue && requestData.SortByDesc.Value;
                    if (ProcessOrder(sortField, isDesc, ref query))
                    {
                        responseData.Data.SortField = sortField;
                        if (isDesc)
                        {
                            responseData.Data.SortByDesc = isDesc;
                        }
                    }
                }

                if (requestData != null && requestData.NeedPage())
                {
                    int pageSize = requestData.PageSize.Value;
                    int pageCount = total / pageSize;
                    int lastPageSize = total % pageSize;
                    if (lastPageSize != 0)
                    {
                        pageCount++;
                    }
                    else
                    {
                        lastPageSize = pageSize;
                    }

                    int pageNo = requestData.PageNo.HasValue ? requestData.PageNo.Value : 1;
                    if (pageNo > pageCount)
                    {
                        pageNo = pageCount;
                    }

                    if (pageNo < 1)
                    {
                        pageNo = 1;
                    }

                    int count = pageSize;
                    if (pageNo == pageCount)
                    {
                        count = lastPageSize;
                    }

                    if (pageNo > 1)
                    {
                        query = query.Skip((pageNo - 1) * pageSize);
                    }

                    query = query.Take(count);

                    responseData.Data.PageCount = pageCount;
                    responseData.Data.PageNo = pageNo;
                    responseData.Data.PageSize = pageSize;
                }

                responseData.Data.List.AddRange(query.ToArray());
            }

            return true;
        }

        protected virtual bool ProcessOrder(string sortField, bool isDesc, ref IQueryable<T> query)
        {
            return false;
        }

        protected virtual string GetDefaultSortField()
        {
            return "id";
        }

        protected virtual Expression<Func<T, bool>> GetWhereExpression(NameValueCollection nameValues)
        {
            return c => true;
        }

        #endregion

        public Expression<Func<T, bool>> WhereExpression(string name, string value)
        {
            PropertyInfo property = typeof(T).GetProperty(name);
            object valueObject = null;
            switch (Type.GetTypeCode(property.PropertyType))
            { 
                case TypeCode.Boolean:
                    valueObject = Convert.ToBoolean(value);
                    break;
                case TypeCode.Byte:
                    valueObject = Convert.ToByte(value);
                    break;
                case TypeCode.Char:
                    valueObject = Convert.ToChar(value);
                    break;
                case TypeCode.DateTime:
                    valueObject = Convert.ToDateTime(value);
                    break;
                case TypeCode.Decimal:
                    valueObject = Convert.ToDecimal(value);
                    break;
                case TypeCode.Double:
                    valueObject = Convert.ToDouble(value);
                    break;
                case TypeCode.Int16:
                    valueObject = Convert.ToInt16(value);
                    break;
                case TypeCode.Int32:
                    valueObject = Convert.ToInt32(value);
                    break;
                case TypeCode.Int64:
                    valueObject = Convert.ToInt64(value);
                    break;
                case TypeCode.SByte:
                    valueObject = Convert.ToSByte(value);
                    break;
                case TypeCode.Single:
                    valueObject = Convert.ToSingle(value);
                    break;
                case TypeCode.String:
                    valueObject = value;
                    break;
                case TypeCode.UInt16:
                    valueObject = Convert.ToUInt16(value);
                    break;
                case TypeCode.UInt32:
                    valueObject = Convert.ToUInt32(value);
                    break;
                case TypeCode.UInt64:
                    valueObject = Convert.ToUInt64(value);
                    break;
                case TypeCode.DBNull:
                case TypeCode.Empty:
                case TypeCode.Object:
                default:
                    break;
            }

            var method = property.PropertyType.GetMethod("Equals");
            return WhereExpression(property, method, valueObject);
        }

        public Expression<Func<T, bool>> WhereExpression(PropertyInfo property, MethodInfo method, object filter)
        {
            var param = Expression.Parameter(typeof(T), "o");
            var propExpr = Expression.Property(param, property);
            var methodExpr = Expression.Call(propExpr, method, Expression.Constant(filter));
            return Expression.Lambda<Func<T, bool>>(methodExpr, param);
        }

        Expression<Func<T, T>> SelectExpression(PropertyInfo property)
        {
            var param = Expression.Parameter(typeof(T), "o");
            var propExpr = Expression.Property(param, property);
            return Expression.Lambda<Func<T, T>>(propExpr, param);
        }
    }
}